﻿using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Cyss.Core.Repository.EF
{

    public class UpdateQueryable<TSource>
    {
        private MethodCallExpression Body = null;

        public IQueryable<TSource> Query { set; get; }

        private int _UpdatePropertyCount;

        public int UpdatePropertyCount { get { return this._UpdatePropertyCount; } }


        private ParameterExpression setParam = Expression.Parameter(typeof(SetPropertyCalls<>).MakeGenericType(typeof(TSource)), "s");

        private ParameterExpression objParam = Expression.Parameter(typeof(TSource), "e");

        public UpdateQueryable(IQueryable<TSource> Query)
        {
            this.Query=Query;
        }

        internal void Add<TProperty>(Expression<Func<TSource, TProperty>> propertyExpression, TProperty valueExpressions)
        {
            _UpdatePropertyCount++;
            var propExpression = Expression.PropertyOrField(objParam, (propertyExpression.Body as MemberExpression).Member.Name);
            var valueExpression = DynamicRelationalExtensions.ValueForType(propertyExpression.ReturnType, valueExpressions);
            if (Body==null)
            {
                Body = Expression.Call(setParam, nameof(SetPropertyCalls<object>.SetProperty), new[] { propertyExpression.ReturnType },
                Expression.Lambda(propExpression, objParam), valueExpression);
            }
            else
            {
                Body = Expression.Call(Body, nameof(SetPropertyCalls<object>.SetProperty),
               new[] { propertyExpression.ReturnType }, Expression.Lambda(propExpression, objParam), valueExpression);
            }
        }

        public LambdaExpression GetBody()
        {
            return Expression.Lambda(Body, setParam);

        }
    }

    public static class DynamicRelationalExtensions
    {



        static MethodInfo UpdateMethodInfo =
        typeof(RelationalQueryableExtensions).GetMethod(nameof(RelationalQueryableExtensions.ExecuteUpdate));
        static MethodInfo UpdateAsyncMethodInfo =
            typeof(RelationalQueryableExtensions).GetMethod(nameof(RelationalQueryableExtensions.ExecuteUpdateAsync));

        public static UpdateQueryable<TSource> SetUpdate<TSource, TProperty>(this IQueryable<TSource> query, Expression<Func<TSource, TProperty>> propertyExpression, TProperty valueExpression)
        {
            UpdateQueryable<TSource> updateQueryable = new UpdateQueryable<TSource>(query);
            updateQueryable.Add(propertyExpression, valueExpression);
            return updateQueryable;
        }

        public static UpdateQueryable<TSource> SetUpdate<TSource, TProperty>(this UpdateQueryable<TSource> updateQueryable, Expression<Func<TSource, TProperty>> propertyExpression, TProperty valueExpression)
        {
            updateQueryable.Add(propertyExpression, valueExpression);
            return updateQueryable;
        }

        /// <summary>
        /// 执行更新
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TProperty"></typeparam>
        /// <param name="updateQueryable"></param>
        /// <returns></returns>
        public static int Execute<TSource>(this UpdateQueryable<TSource> updateQueryable)
        {

            return (int)UpdateMethodInfo.MakeGenericMethod(updateQueryable.Query.ElementType).Invoke(null, new object?[] { updateQueryable.Query, updateQueryable.GetBody() });
        }

        public static int ExecuteUpdate(this IQueryable query, Dictionary<string, object> dictionar)
        {
            var updateBody = BuildUpdateBody(query.ElementType, dictionar);
            return (int)UpdateMethodInfo.MakeGenericMethod(query.ElementType).Invoke(null, new object?[] { query, updateBody });
        }

        public static int ExecuteUpdate<TSource, TProperty>(this IQueryable<TSource> queryable, Func<TSource, TProperty> propertyExpression, TProperty valueExpression)
        {
            return queryable.ExecuteUpdate(x => x.SetProperty(propertyExpression, valueExpression));
        }

        public static int ExecuteUpdate(this IQueryable query, string fieldName, object fieldValue)
        {
            var updateBody = BuildUpdateBody(query.ElementType, fieldName, fieldValue);
            return (int)UpdateMethodInfo.MakeGenericMethod(query.ElementType).Invoke(null, new object?[] { query, updateBody });
        }

        static LambdaExpression BuildUpdateBody(Type entityType, Dictionary<string, object> dictionar)
        {
            var setParam = Expression.Parameter(typeof(SetPropertyCalls<>).MakeGenericType(entityType), "s");
            var objParam = Expression.Parameter(entityType, "e");
            MethodCallExpression body = null;
            foreach (var item in dictionar)
            {
                var propExpression = Expression.PropertyOrField(objParam, item.Key);
                var valueExpression = ValueForType(propExpression.Type, item.Value);
                // s.SetProperty(e => e.SomeField, value)

                if (body==null)
                {
                    body = Expression.Call(setParam, nameof(SetPropertyCalls<object>.SetProperty),
                    new[] { propExpression.Type }, Expression.Lambda(propExpression, objParam), valueExpression);
                }
                else
                {
                    body = Expression.Call(body, nameof(SetPropertyCalls<object>.SetProperty),
                   new[] { propExpression.Type }, Expression.Lambda(propExpression, objParam), valueExpression);
                }
            }
            var updateBody = Expression.Lambda(body, setParam);

            // s => s.SetProperty(e => e.SomeField, value)
            return updateBody;
        }


        static LambdaExpression BuildUpdateBody(Type entityType, string fieldName, object? fieldValue)
        {
            var setParam = Expression.Parameter(typeof(SetPropertyCalls<>).MakeGenericType(entityType), "s");
            var objParam = Expression.Parameter(entityType, "e");
            var propExpression = Expression.PropertyOrField(objParam, fieldName);
            var valueExpression = ValueForType(propExpression.Type, fieldValue);
            // s.SetProperty(e => e.SomeField, value)
            var setBody = Expression.Call(setParam, nameof(SetPropertyCalls<object>.SetProperty),
                new[] { propExpression.Type }, Expression.Lambda(propExpression, objParam), valueExpression);

            // s => s.SetProperty(e => e.SomeField, value)
            var updateBody = Expression.Lambda(setBody, setParam);
            return updateBody;
        }

        public static Expression ValueForType(Type desiredType, object? value)
        {
            if (value == null)
            {
                return Expression.Default(desiredType);
            }
            if (value.GetType() != desiredType)
            {
                value = Convert.ChangeType(value, desiredType);
            }
            return ToExpression(value);
        }

        static Expression ToExpression(object value)
        {
            if (value == null) return Expression.Constant(null);
            var valueType = value.GetType();
            var closureType = typeof(Tuple<>).MakeGenericType(valueType);
            var closure = Activator.CreateInstance(closureType, value);
            return Expression.Property(Expression.Constant(closure), "Item1");
        }
    }
}
